#include "TextPreprocessor.hpp"
#include "_smileys_array.cpp"

#ifdef INLINE_TEST
	#include "Url.cpp"
	#include "../hash.cpp"
#endif

const char TextPreprocessor::hex_chars[16] = {
	'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 
	'A', 'B', 'C', 'D', 'E', 'F'
}; 

const TextPreprocessor::BBCodeTagMeta TextPreprocessor::tags_meta[] = {
	BBCODE_TAG(BBCODE_BREAK,      "<br />\n", "", "",                                              BBCODE_FLAG_IS_SINGLE), 
	BBCODE_TAG(BBCODE_BOLD,       "<b>", "", "</b>",                                               BBCODE_FLAG_INNER_TAGS), 
	BBCODE_TAG(BBCODE_ITALIC,     "<i>", "", "</i>",                                               BBCODE_FLAG_INNER_TAGS), 
	BBCODE_TAG(BBCODE_STRIKE,     "<span style=\"text-decoration: line-through\">", "", "</span>", BBCODE_FLAG_INNER_TAGS), 
	BBCODE_TAG(BBCODE_UNDERLINE,  "<span style=\"text-decoration: underline\">", "", "</span>",    BBCODE_FLAG_INNER_TAGS), 
	BBCODE_TAG(BBCODE_URL,        "<a href=\"", "\">", "</a>",                                     BBCODE_FLAG_INNER_TAGS | BBCODE_FLAG_REQUIRE_VALUE), 
	BBCODE_TAG(BBCODE_URL_SINGLE, "<a href=\"", "\">", "</a>",                                     BBCODE_FLAG_NONE), 
	BBCODE_TAG(BBCODE_COLOR,      "<span style=\"color: ", "\">", "</span>",                       BBCODE_FLAG_INNER_TAGS | BBCODE_FLAG_REQUIRE_VALUE), 
	BBCODE_TAG(BBCODE_BGCOLOR,    "<div style=\"background-color: ", "\">", "</div>",              BBCODE_FLAG_INNER_TAGS | BBCODE_FLAG_REQUIRE_VALUE), 
	BBCODE_TAG(BBCODE_SPOILER,    "<div class=\"sp_wrap\"><div class=\"sp_name\"><a href=\"#\" onclick=\"spoiler(this)\">", "</a></div><div class=\"sp_body\">", "</div>",                             BBCODE_FLAG_INNER_TAGS | BBCODE_FLAG_REQUIRE_VALUE), 
	BBCODE_TAG(BBCODE_CODE,       "<div class=\"code\">", "", "</div>",                            BBCODE_FLAG_NONE), 
	BBCODE_TAG(BBCODE_USER,       "<a href=\"", "\">", "</a>",                                     BBCODE_FLAG_INNER_TAGS | BBCODE_FLAG_IS_SINGLE | BBCODE_FLAG_REQUIRE_VALUE), 
	BBCODE_TAG(BBCODE_SMILE,      "<img src=\"", "\" alt="" />", "",                               BBCODE_FLAG_DISABLED)
}; 

unsigned int TextPreprocessor::findUnicodeStart(char *text, unsigned int pos) {
	if ((text[pos] >> 7 & 0x1) == 0)
		return pos; 
	while (--pos && (text[pos] >> 7 & 0x1) == 1 && (text[pos] >> 6 & 0x1) == 0); 
	return pos; 
}

unsigned int TextPreprocessor::findUnicodeEnd(char *text, unsigned int pos, unsigned int len) {
	if ((text[pos] >> 7 & 0x1) == 0)
		return pos; 
	while (++pos < len && (text[pos] >> 7 & 0x1) == 1 && (text[pos] >> 6 & 0x1) == 0); 
	return pos; 
}

// Выпилино и успешно заюзан самопальный класс Url
/*
unsigned short TextPreprocessor::normalizeUrlCalculateLength(char *str, unsigned short str_length) {
	unsigned int new_len = str_length; 
	for (unsigned short i = 0; i < str_length; ++i)
		if (!isprint(str[i]) || IS_BREAK(str[i]))
			new_len += 2; 
	return new_len; 
}

char *TextPreprocessor::normalizeUrl(char *str, unsigned short str_length, unsigned short *new_str_length, bool *need_to_free) {
	*new_str_length  = normalizeUrlCalculateLength(str, str_length) + 4; // sid=hash
	*need_to_free = *new_str_length != str_length; 
	if (!*need_to_free)
		return str; 
	char *new_buffer   = (char *)malloc(*new_str_length); 
	char *p_new_buffer = new_buffer; 
	
	for (unsigned short i = 0; i < str_length; i++) {
		if (!isprint(str[i]) || IS_BREAK(str[i])) {
			*p_new_buffer++ = '%'; 
			*p_new_buffer++ = hex_chars[str[i] >> 4 & 0xF]; 
			*p_new_buffer++ = hex_chars[str[i] & 0xF]; 
		} else
			*p_new_buffer++ = str[i]; 
	}
	
	return new_buffer; 
}

unsigned short TextPreprocessor::_prepareRedirectUrlCalculateLength(char *str, unsigned short str_length, bool wrap) {
	unsigned int new_len = str_length; 
	for (unsigned short i = 0; i < str_length; ++i) {
		if (wrap && str[i] == '&' && i + 3 < str_length) {
			if (STRCMP_CONST(str + i + 1, "amp;") == 0) {
				new_len -= 2; i += 4; 
				continue; 
			} else if (STRCMP_CONST(str + i + 1, "quot;") == 0) {
				new_len -= 3; i += 5; 
				continue; 
			} else if (STRCMP_CONST(str + i + 1, "039;") == 0) {
				new_len -= 2; i += 4; 
				continue; 
			} else if (STRCMP_CONST(str + i + 1, "lt;") == 0) {
				new_len -= 1; i += 3; 
				continue; 
			} else if (STRCMP_CONST(str + i + 1, "gt;") == 0) {
				new_len -= 1; i += 3; 
				continue; 
			}
		}
		
		if (!(
			(str[i] >= 'A' && str[i] <= 'Z') || (str[i] >= 'a' && str[i] <= 'z') || 
			(str[i] >= '0' && str[i] <= '9') || 
			(str[i] == '-' || str[i] == '_' || str[i] == '.' || str[i] == '~')
		))
			new_len += 2; 
	}
	return new_len; 
}

char *TextPreprocessor::prepareRedirectUrl(char *str, unsigned short str_length, unsigned short *new_str_length, bool *need_to_free, bool wrap) {
	// *new_str_length  = _prepareRedirectUrlCalculateLength(str, str_length, wrap) + redirect_url_length; 
	*need_to_free = *new_str_length - str_length != 0; 
	char *new_buffer = *need_to_free ? (char *)malloc(*new_str_length) : str; 
	char *p_new_buffer = new_buffer; 
	
	//if (redirect_url_length) {
	//	memcpy(new_buffer, redirect_url, redirect_url_length); 
	//	p_new_buffer += redirect_url_length; 
	//}
	
	for (size_t i = 0; i < str_length; i++) {
		if (wrap && str[i] == '&' && i + 1 < str_length) {
			if (STRCMP_CONST(str + i + 1, "amp;") == 0) {
				*p_new_buffer++ = '%'; 
				*p_new_buffer++ = '2'; 
				*p_new_buffer++ = '6'; 
				i += 4; 
				continue; 
			} else if (STRCMP_CONST(str + i + 1, "quot;") == 0) {
				*p_new_buffer++ = '%'; 
				*p_new_buffer++ = '2'; 
				*p_new_buffer++ = '2'; 
				i += 5; 
				continue; 
			} else if (STRCMP_CONST(str + i + 1, "039;") == 0) {
				*p_new_buffer++ = '%'; 
				*p_new_buffer++ = '2'; 
				*p_new_buffer++ = '7'; 
				i += 4; 
				continue; 
			} else if (STRCMP_CONST(str + i + 1, "lt;") == 0) {
				*p_new_buffer++ = '%'; 
				*p_new_buffer++ = '3'; 
				*p_new_buffer++ = 'C'; 
				i += 3; 
				continue; 
			} else if (STRCMP_CONST(str + i + 1, "gt;") == 0) {
				*p_new_buffer++ = '%'; 
				*p_new_buffer++ = '3'; 
				*p_new_buffer++ = 'E'; 
				i += 3; 
				continue; 
			}
		}
		
		if (str[i] == ' ')
			*p_new_buffer++ = '+'; 
		else if (!(
			(str[i] >= 'A' && str[i] <= 'Z') || (str[i] >= 'a' && str[i] <= 'z') || 
			(str[i] >= '0' && str[i] <= '9') || 
			(str[i] == '-' || str[i] == '_' || str[i] == '.' || str[i] == '~')
		)) {
			*p_new_buffer++ = '%'; 
			*p_new_buffer++ = hex_chars[str[i] >> 4 & 0xF]; 
			*p_new_buffer++ = hex_chars[str[i] & 0xF]; 
		} else if (*need_to_free)
			*p_new_buffer++ = str[i]; 
	}
	return new_buffer; 
}
*/

short TextPreprocessor::smileToId(const char *smile) {
	unsigned int hash = crc32_string(smile); 
	size_t size = ARRAY_SIZE(smileys_names); 
	while (size--)
		if (smileys_names[size] == hash)
			return size; 
	return -1; 
}

char TextPreprocessor::tagToId(const char *tag, bool required_value) {
	size_t size = ARRAY_SIZE(tags_meta); 
	while (size--)
		if ((bool)(tags_meta[size].flags & BBCODE_FLAG_REQUIRE_VALUE) == required_value)
			if (strncmp(tags_meta[size].name, tag, BBCODE_MAX_TAG_LENGTH) == 0)
				return size; 
	return -1; 
}

bool TextPreprocessor::isValidColor(const char *color) {
	while (*color) {
		if (!(*color >= '0' && *color <= '9') && !(*color >= 'a' && *color <= 'z') && !(*color >= 'A' && *color <= 'Z') && *color != '#')
			return false; 
		++color; 
	}
	return true; 
}

// Анализатор текста (поиск тегов и т.д.)
void TextPreprocessor::analize() {
	if (state & TP_STATE_BBCODE_PREPARED)
		return; 
	vector<BBCodeTag> tmp_tags; 
	unsigned int  start_tag_offset = 0; 
	unsigned char state            = TPA_NONE; 
	
	int  tag_id; 
	char tag_name[BBCODE_MAX_TAG_LENGTH + 1]; 
	char tag_value[BBCODE_MAX_VAL_LENGTH + 1]; 
	
	char *p_tag_name  = (char *)tag_name; 
	char *p_tag_value = (char *)tag_value; 
	
	char *sp_tag_name  = p_tag_name; 
	char *sp_tag_value = p_tag_value; 
	
	text_global_delta = 0; 
	
	if (tags.size() > 0)
		freeTags(tags); 
	
	for (unsigned int i = 0; i < text_length; ++i) {
		if (text[i] == '\0')
			continue; 
		
		if (!(state & TPA_VAL)) {
			// Тег юзера
			if (text[i] == '@' && (!i || IS_BREAK(text[i - 1])) && i + MIN_USER_NAME < text_length) {
				start_tag_offset = i; 
				p_tag_value = sp_tag_value; 
				++i; 
				while (i < text_length && NICK_VALIDATE(text[i]) && 
					p_tag_value - sp_tag_value + 1 <= MAX_USER_NAME) 
					*p_tag_value++ = text[i++]; 
				*p_tag_value = 0; 
				
				if (i >= text_length || IS_BREAK(text[i])) {
					tags.resize(tags.size() + 1); 
					BBCodeTag &tag = tags[tags.size() - 1]; 
					NEW_TAG(tag, start_tag_offset, i, 0, 0); 
					NEW_TAG_META(
						tag.meta, BBCODE_USER_ID, strdup(tag_value), 
						p_tag_value - sp_tag_value
					); 
					
					text_global_delta += 
						(tags_meta[BBCODE_USER_ID].open_tag_length
						+ tags_meta[BBCODE_USER_ID].open_tag2_length
						+ tags_meta[BBCODE_USER_ID].close_tag_length
						+ tag.meta -> value_length + tag.meta -> value_length
						+ BBCODE_MAX_URL_LENGTH)
						- (tag.end_open - tag.start_open); 
				}
				p_tag_value = sp_tag_value; 
			}
			
			// Перенос строки
			if (text[i] == '\n') {
				tags.resize(tags.size() + 1); 
				BBCodeTag &tag = tags[tags.size() - 1]; 
				NEW_TAG(tag, i, i + 1, 0, 0); 
				NEW_TAG_META(tag.meta, BBCODE_BREAK_ID, NULL, 0); 
				if (i && text[i - 1] == '\r') // Если это windows-перенос строки (\r\n)
					--tag.start_open; 
				text_global_delta += tags_meta[BBCODE_BREAK_ID].open_tag_length - 
					(tag.end_open - tag.start_open); 
				continue; 
			}
			
			// Простая ссылка без тега [url]
			if (text[i] == 'h' && IS_URL_PART(text, i) && i + 11 < text_length) {
				start_tag_offset = i; 
				p_tag_value = sp_tag_value; 
				// Парсинг URL-а до пробельного или контрольного символа
				while (i < text_length && !IS_BREAK(text[i]) && 
					p_tag_value - sp_tag_value + 1 <= BBCODE_MAX_VAL_LENGTH)
					*p_tag_value++ = text[i++]; 
				*p_tag_value = 0; 
				
				if (i >= text_length || IS_BREAK(text[i])) {
					tags.resize(tags.size() + 1); 
					BBCodeTag &tag = tags[tags.size() - 1]; 
					NEW_TAG(tag, start_tag_offset, i, 0, 0); 
					NEW_TAG_META(
						tag.meta, BBCODE_URL_ID, strdup(tag_value), 
						p_tag_value - sp_tag_value
					); 
					
					text_global_delta += 
						(tags_meta[BBCODE_URL_ID].open_tag_length
						+ tags_meta[BBCODE_URL_ID].open_tag2_length
						+ tags_meta[BBCODE_URL_ID].close_tag_length
						+ tag.meta -> value_length + tag.meta -> value_length) 
						- (tag.end_open - tag.start_open); 
				}
				p_tag_value = sp_tag_value; 
			}
			
			// Смайлы
			if (!(flags & TP_FLAGS_NO_SMILEYS) && i + BBCODE_MIN_SML_LENGTH < text_length && (
				text[i] == ':' || (
					// TODO: вынести куда-то в автогенерируемые макросы
					
					// ;) и ;-)
					((text[i] == ';' || text[i] == ':') && ((text[i + 1] == '-' && (text[i + 2] == ')' || text[i + 2] == '(')) || (text[i + 2] == ')' || text[i + 2] == '('))) || 
					// 8-O и 8-o
					(text[i] == '8' && (text[i + 1] == '-' && (text[i + 2] == 'O' || text[i + 2] == 'o' || text[i + 2] == '8'))) ||
					// =)
					(text[i] == '=' && text[i + 1] == ')') || 
					// o.O и o_O
					(text[i] == 'o' && (text[i + 1] == '.' || text[i + 1] == '_') && text[i + 2] == 'O')
				)
			)) {
				start_tag_offset = i; 
				p_tag_value = sp_tag_value; 
				while (i < text_length && !IS_BREAK(text[i]) &&
					p_tag_value - sp_tag_value + 1 <= BBCODE_MAX_SML_LENGTH)
					*p_tag_value++ = text[i++]; 
				*p_tag_value = 0; 
				
				short smile_id; 
				if (p_tag_value - sp_tag_value >= BBCODE_MIN_SML_LENGTH && (smile_id = smileToId(tag_value)) != -1) {
					tags.resize(tags.size() + 1); 
					BBCodeTag &tag = tags[tags.size() - 1]; 
					NEW_TAG(tag, start_tag_offset, i, 0, 0); 
					NEW_TAG_META(
						tag.meta, BBCODE_SMILE_ID, strdup(smileys_files[smile_id]), 
						strlen(smileys_files[smile_id])
					); 
					
					text_global_delta += 
						(tags_meta[BBCODE_SMILE_ID].open_tag_length
						+ tags_meta[BBCODE_SMILE_ID].open_tag2_length
						+ smileys_url_length + tag.meta -> value_length)
						- (tag.end_open - tag.start_open); 
				} else
					i = start_tag_offset; // нифига не смайл
				p_tag_value = sp_tag_value; 
			}
		}
		
		if (!(state & TPA_VAL) && text[i] == '[') {
			state |= TPA_ESC; 
			start_tag_offset = i; 
			continue; 
		}
		
		if (text[i] == '=' && (state & TPA_ESC) && !(state & TPA_END) && !(state & TPA_VAL)) {
			state |= TPA_VAL; 
			continue; 
		}
		
		if (text[i] == '/' && (state & TPA_ESC) && !(state & TPA_END) && !(state & TPA_VAL)) {
			state |= TPA_END; 
			continue; 
		}
		
		if (text[i] == ']') {
			*p_tag_name  = NULL; 
			*p_tag_value = NULL; 
			
			if ((state & TPA_END)) {
				size_t j = tmp_tags.size(); 
				while (j--) {
					BBCodeTag &tag_back = tmp_tags.at(j); 
					tag_id = tagToId(tag_name, (tags_meta[tag_back.meta -> id].flags & BBCODE_FLAG_REQUIRE_VALUE)); 
					if (tag_id != -1 && !tmp_tags.empty() && tag_back.meta -> id == (size_t)tag_id) {
						tags.resize(tags.size() + 1); 
						BBCodeTag &tag = tags[tags.size() - 1]; 
						NEW_TAG(tag, tag_back.start_open, tag_back.end_open, start_tag_offset, i + 1); 
						
						if ((tags_meta[tag_id].flags & BBCODE_FLAG_REQUIRE_VALUE)) {
							NEW_TAG_META(
								tag.meta, tag_back.meta -> id, strdup(tag_back.meta -> value), 
								tag_back.meta -> value_length
							); 
						} else {
							NEW_TAG_META(tag.meta, tag_back.meta -> id, NULL, 0); 
						}
						
						text_global_delta += (
							tags_meta[tag_id].open_tag_length + 
							tags_meta[tag_id].open_tag2_length + 
							tag.meta -> value_length
						) - (tag.end_open - tag.start_open); 
						text_global_delta += 
							tags_meta[tag_id].close_tag_length - (tag.end_close - tag.start_close); 
						
						deleteTag(tmp_tags, j); 
						break; 
					}
				}
			} else {
				tag_id = tagToId(tag_name, p_tag_value - sp_tag_value > 0); 
				if (tag_id != -1 && !(tags_meta[tag_id].flags & BBCODE_FLAG_DISABLED)) {
					if ((tags_meta[tag_id].flags & BBCODE_FLAG_IS_SINGLE)) {
						tags.resize(tags.size() + 1); 
						BBCodeTag &tag = tags[tags.size() - 1]; 
						NEW_TAG(tag, start_tag_offset, i + 1, 0, 0); 
						
						if ((tags_meta[tag_id].flags & BBCODE_FLAG_REQUIRE_VALUE)) {
							NEW_TAG_META(tag.meta, tag_id, strdup(tag_value), 
								p_tag_value - sp_tag_value); 
						} else {
							NEW_TAG_META(tag.meta, tag_id, NULL, 0); 
						}
						
						text_global_delta += (
							tags_meta[tag_id].open_tag_length + 
							tags_meta[tag_id].open_tag2_length + 
							tag.meta -> value_length
						) - (tag.end_open - tag.start_open); 
					} else {
						// Нельзя иметь одинаковые вложенные теги. Удаляем их. 
						for (size_t j = 0; j < tmp_tags.size(); ++j) {
							BBCodeTag &tag_back = tmp_tags.at(j); 
							if (tag_back.meta -> id == (size_t)tag_id) {
								deleteTag(tmp_tags, j); 
								--j; 
								break; 
							}
						}
						tmp_tags.resize(tmp_tags.size() + 1); 
						BBCodeTag &tag = tmp_tags[tmp_tags.size() - 1]; 
						NEW_TAG(tag, start_tag_offset, i + 1, 0, 0); 
						NEW_TAG_META(tag.meta, tag_id, strdup(tag_value), 
							p_tag_value - sp_tag_value); 
					}
				}
			}
			
			state &= ~(TPA_END | TPA_ESC | TPA_VAL); 
			
			p_tag_value  = sp_tag_value; 
			*p_tag_value = 0; 
			p_tag_name   = sp_tag_name; 
			*p_tag_name  = 0; 
			
			start_tag_offset = 0; 
			
			continue; 
		}
		
		if ((state & TPA_ESC)) {
			if ((state & TPA_VAL)) {
				if (p_tag_value - sp_tag_value + 1 <= BBCODE_MAX_VAL_LENGTH)
					*p_tag_value++ = text[i]; 
			} else {
				if (p_tag_name - sp_tag_name + 1 <= BBCODE_MAX_TAG_LENGTH)
					*p_tag_name++ = text[i]; 
			}
		}
	}
	
	freeTags(tmp_tags); 
	unsigned int start_no_inners = 0, end_no_inners = 0; 
	for (std::vector<BBCodeTag>::reverse_iterator it = tags.rbegin(); it != tags.rend(); ++it) {
		BBCodeTag &tag = *it; 
		if (end_no_inners && start_no_inners <= tag.start_open && tag.start_open <= end_no_inners) {
			freeTagData(tag); 
			tags.erase(--it.base());  
			continue; 
		}
		if (tag.start_close && !(tags_meta[tag.meta -> id].flags & BBCODE_FLAG_INNER_TAGS)) {
			start_no_inners = tag.end_open; 
			end_no_inners   = tag.start_close; 
		}
	}
	
	this -> state |= TP_STATE_BBCODE_ANALIZED; 
}

void TextPreprocessor::resize(unsigned int start, unsigned int size, bool pointers) {
	if (state & TP_STATE_BBCODE_PREPARED || !state & TP_STATE_BBCODE_ANALIZED)
		return; 
	unsigned int end = start + size; 
	string additional_start; 
	string additional_end; 
	
	for (size_t i = 0; i < tags.size(); ++i) {
		BBCodeTag &tag = tags.at(i); 
		
		// Не влезает открывающий в начале
		if (tag.start_open <= start) {
			if (tag.end_open >= start) {
				start = tag.start_open; 
			} else if (tag.start_close > 0 && tag.start_close >= start) {
				if (tag.start_close == start)
					start = tag.start_open; 
				else
					additional_start.insert(0, text + tag.start_open, tag.end_open - tag.start_open); 
			}
		}
		
		// Не влезает открывающий в конце
		if (tag.end_open > end && tag.start_open <= end)
			end = tag.end_open; 
		
		if (tag.end_close > 0) {
			// Не влезает закрывающий в конце
			if (tag.end_close >= end) {
				if (tag.start_close <= end) {
					end = tag.end_close; 
				} else if (tag.end_open <= end) {
					if (tag.end_open == end)
						end = tag.end_close; 
					else
						additional_end.append(text + tag.start_close, tag.end_close - tag.start_close); 
				}
			}
			
			// Не влезает закрывающий в начале
			if (tag.start_close < start && tag.end_close >= start)
				start = tag.end_close; 
		}
	}
	
	start = findUnicodeStart(text, start); 
	end   = findUnicodeEnd(text, end, text_length); 
	
	if (end != text_length - 1 && pointers)
		additional_end.append(SPLITTER_POINTERS, sizeof(SPLITTER_POINTERS) - 1); 
	if (start && pointers)
		additional_start.insert(0, SPLITTER_POINTERS, sizeof(SPLITTER_POINTERS) - 1); 
	
	text_length = (end - start) + additional_start.length() + additional_end.length(); 
	
	if (text_length + 1 > text_allocated) {
		text = (char *)realloc(text, text_length + 1); 
		text_allocated = text_length + 1; 
	}
	
	if (text + start != text + additional_start.length())
		memmove(text + additional_start.length(), text + start, end - start); 
	if (additional_start.length())
		memcpy(text, additional_start.c_str(), additional_start.length()); 
	if (additional_end.length())
		memcpy(text + (end - start) + additional_start.length(), additional_end.c_str(), additional_end.length()); 
	
	if (text_allocated != text_length + 1) {
		text = (char *)realloc(text, text_length + 1); 
		text_allocated = text_length + 1; 
	}
	
	text[text_length] = 0; 
	analize(); 
	this -> state |= TP_STATE_BBCODE_SPLITTED; 
}

// Обработка всех тегов
void TextPreprocessor::prepare() {
	if ((state & TP_STATE_BBCODE_PREPARED) || !(state & TP_STATE_BBCODE_ANALIZED))
		return; 
	Url tmp_url; 
	
	if (text_global_delta > 0) {
		text_allocated += text_global_delta; 
		text = (char *)realloc(text, text_allocated); 
	}
	
	for (size_t i = 0; i < tags.size(); ++i) {
		BBCodeTag &tag = tags.at(i); 
		switch (tag.meta -> id) {
			case BBCODE_BREAK_ID: 
			case BBCODE_BOLD_ID: 
			case BBCODE_ITALIC_ID: 
			case BBCODE_STRIKE_ID: 
			case BBCODE_UNDERLINE_ID: 
			case BBCODE_CODE_ID: 
				replace (
					tags_meta[tag.meta -> id].open_tag, tags_meta[tag.meta -> id].open_tag_length, 
					NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, tag.start_open, tag.end_open, i
				); 
				if (tags_meta[tag.meta -> id].close_tag_length > 0) {
					replace (
						tags_meta[tag.meta -> id].close_tag, tags_meta[tag.meta -> id].close_tag_length, 
						NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, tag.start_close, tag.end_close, i
					); 
				}
			break; 
			
			case BBCODE_URL_SINGLE_ID: 
			case BBCODE_URL_ID: 
			{
				if (!tag.meta -> value_length > 0) {
					if (tag.meta -> value)
						::free(tag.meta -> value); 
					tag.meta -> value_length = tag.start_close - tag.end_open; 
					tag.meta -> value        = strndup(text + tag.end_open, tag.meta -> value_length); 
				}
				
				tmp_url.parse(tag.meta -> value, tag.meta -> value_length); 
				bool is_ext_link = !strncasecmp(tmp_url.getHost(), home_url.getHost(), home_url.getHostLength()) == 0; 
				if (is_ext_link && (flags & TP_FLAGS_NO_EXT_LINKS))
					break; 
				
				if (!is_ext_link)
					tmp_url["sid"] = session_id; 
				string &&url_string = tmp_url.toString(); 
				
				if (is_ext_link) {
					redirect_url["url"].assign(tag.meta -> value, tag.meta -> value_length); 
					url_string = redirect_url.toString(); 
				}
				
				if (tag.end_close) {
					replace (
						tags_meta[tag.meta -> id].open_tag, tags_meta[tag.meta -> id].open_tag_length, 
						url_string.c_str(), url_string.length(), 
						tags_meta[tag.meta -> id].open_tag2, tags_meta[tag.meta -> id].open_tag2_length, 
						NULL, 0, NULL, 0, NULL, 0, tag.start_open, tag.end_open, i
					); 
					replace (
						tags_meta[tag.meta -> id].close_tag, tags_meta[tag.meta -> id].close_tag_length, 
						NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, tag.start_close, tag.end_close, i
					); 
				} else {
					string &&display_url_string = (string)url_string; 
					if (!is_ext_link) {
						tmp_url["sid"] = ""; 
						display_url_string = tmp_url.toString(); 
					}
					
					replace (
						tags_meta[tag.meta -> id].open_tag, tags_meta[tag.meta -> id].open_tag_length, 
						url_string.c_str(), url_string.length(), 
						tags_meta[tag.meta -> id].open_tag2, tags_meta[tag.meta -> id].open_tag2_length, 
						display_url_string.c_str(), display_url_string.length(), 
						tags_meta[tag.meta -> id].close_tag, tags_meta[tag.meta -> id].close_tag_length, 
						NULL, 0, tag.start_open, tag.end_open, i
					); 
				}
			}
			break; 
			
			case BBCODE_SMILE_ID: 
			{
				replace (
					tags_meta[tag.meta -> id].open_tag, tags_meta[tag.meta -> id].open_tag_length, 
					smileys_url, smileys_url_length, 
					tag.meta -> value, tag.meta -> value_length, 
					tags_meta[tag.meta -> id].open_tag2, tags_meta[tag.meta -> id].open_tag2_length, 
					NULL, 0, NULL, 0, 
					tag.start_open, tag.end_open, i
				); 
			}
			break; 
			
			case BBCODE_USER_ID: 
			{
				user_url["nick"].assign(tag.meta -> value, tag.meta -> value_length); 
				const string &user_url_link = user_url.toString(); 
				replace (
					tags_meta[tag.meta -> id].open_tag, tags_meta[tag.meta -> id].open_tag_length, 
					user_url_link.c_str(), user_url_link.length(), 
					tags_meta[tag.meta -> id].open_tag2, tags_meta[tag.meta -> id].open_tag2_length, 
					tag.meta -> value, tag.meta -> value_length, 
					tags_meta[tag.meta -> id].close_tag, tags_meta[tag.meta -> id].close_tag_length, 
					NULL, 0, 
					tag.start_open, tag.end_open, i
				); 
			}
			break; 
			
			case BBCODE_SPOILER_ID: 
				replace (
					tags_meta[tag.meta -> id].open_tag, tags_meta[tag.meta -> id].open_tag_length, 
					tag.meta -> value, tag.meta -> value_length, 
					tags_meta[tag.meta -> id].open_tag2, tags_meta[tag.meta -> id].open_tag2_length, 
					NULL, 0, NULL, 0, NULL, 0, tag.start_open, tag.end_open, i
				); 
				replace (
					tags_meta[tag.meta -> id].close_tag, tags_meta[tag.meta -> id].close_tag_length, 
					NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, tag.start_close, tag.end_close, i
				); 
			break; 
			
			case BBCODE_COLOR_ID: 
			case BBCODE_BGCOLOR_ID: 
				if (!isValidColor(tag.meta -> value))
					continue; 
				replace (
					tags_meta[tag.meta -> id].open_tag, tags_meta[tag.meta -> id].open_tag_length, 
					tag.meta -> value, tag.meta -> value_length, 
					tags_meta[tag.meta -> id].open_tag2, tags_meta[tag.meta -> id].open_tag2_length, 
					NULL, 0, NULL, 0, NULL, 0, tag.start_open, tag.end_open, i
				); 
				replace (
					tags_meta[tag.meta -> id].close_tag, tags_meta[tag.meta -> id].close_tag_length, 
					NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, tag.start_close, tag.end_close, i
				); 
			break; 
		}
	}
	
	freeTags(tags); 
	if (text_length + 1 < text_allocated) {
		text_allocated = text_length + 1; 
		text = (char *)realloc(text, text_allocated); 
	}
	
	this -> state |= TP_STATE_BBCODE_PREPARED; 
}

// Замена тега в тексте
// TODO: Сделать или как-то на шаблонах или в макросы хотя бы часть вынести, громоздко таки
void TextPreprocessor::replace (
	const char *tag_start, unsigned short tag_start_len, 
	const char *tag_val, unsigned short tag_val_len, 
	const char *tag_start2, unsigned short tag_start2_len, 
	const char *tag_start3, unsigned short tag_start3_len, 
	const char *tag_start4, unsigned short tag_start4_len, 
	const char *tag_start5, unsigned short tag_start5_len, 
	unsigned int start, unsigned int end, size_t tag_id
) {
	unsigned int replace_length = tag_start_len + tag_val_len + tag_start2_len + 
		tag_start3_len + tag_start4_len + tag_start5_len; 
	int delta = replace_length - (end - start); 
	
	if (delta < 0)
		memmove(text + (end + delta), text + end, text_length - end); 
	if (delta > 0 && text_allocated < text_length + delta + 1) {
		text = (char *)realloc(text, text_length + delta + 1 + TP_BUFFER_SIZE); 
		text_allocated = text_length + delta + 1 + TP_BUFFER_SIZE; 
	}
	*(text + text_length + delta) = 0; 
	
	if (delta > 0)
		memmove(text + (start + replace_length), text + end, text_length - end); 
	
	unsigned int _offset = start; 
	if (tag_start_len) {
		memcpy(text + _offset, tag_start, tag_start_len); 
		_offset += tag_start_len; 
	}
	
	if (tag_val_len) {
		memcpy(text + _offset, tag_val, tag_val_len); 
		_offset += tag_val_len; 
	}
	if (tag_start2_len) {
		memcpy(text + _offset, tag_start2, tag_start2_len); 
		_offset += tag_start2_len; 
	}
	if (tag_start3_len) {
		memcpy(text + _offset, tag_start3, tag_start3_len); 
		_offset += tag_start3_len; 
	}
	if (tag_start4_len) {
		memcpy(text + _offset, tag_start4, tag_start4_len); 
		_offset += tag_start4_len; 
	}
	if (tag_start5) {
		memcpy(text + _offset, tag_start5, tag_start5_len); 
		_offset += tag_start5_len; 
	}
	
	// Тормозит сильно :( 
	if (delta) {
		for (unsigned int i = tag_id; i < tags.size(); ++i) {
			BBCodeTag &tag = tags[i]; 
			if (tag.start_open >= end)
				tag.start_open += delta; 
			if (tag.start_close >= end)
				tag.start_close += delta; 
			if (tag.end_open >= end)
				tag.end_open += delta; 
			if (tag.end_close >= end)
				tag.end_close += delta; 
		}
	}
	text_length += delta; 
}

#ifdef INLINE_TEST
int main() {
	const char text[] = "[bgcolor=#bcbcb3][color=#34ff5d]От нежности, поверь, спасенья нет,[/color]\r\n\
Не будешь думать больше ни о чём..\r\n\
..Ведь приворот сварил ведьмак - поэт ,\r\n\
И больше ты не вспомнишь о былом... :гг :гг [/bgcolor]\r\n\
[bgcolor=#708090][color=#34ff5d]Случится чудо – сказка станет былью ,[/color]\r\n\
И почему – то будет не до сна…\r\n\
И локон твой украсит звёздной пылью,\r\n\
Румяная,весенняя луна… :уу :уу [/bgcolor]\r\n\
[bgcolor=#6c6c6c][color=#34ff5d]Почему так сладко пахнут розы, [/color]\r\n\
Принося сумятицу в сердца?\r\n\
 Аромат цветов рождает грезы,\r\n\
Душу  будоражит без конца. :плакса :плакса [/bgcolor]\r\n\
[bgcolor=#fcc9c9][color=#34ff5d]Боюсь, как дьявольской напасти,[/color]\r\n\
Освободительных забот:\r\n\
Когда рабы приходят к власти,\r\n\
Они куда страшней господ. :) :) [/bgcolor]\r\n\
[bgcolor=#ffc998][color=#34ff5d]Бывает – проснёшся, как птица,[/color]\r\n\
Крылатой пружиной на взводе,\r\n\
И хочется жить и трудиться;\r\n\
Но к завтраку это проходит. :( :( [/bgcolor]\r\n\
[bgcolor=#ff9331][color=#34ff5d]Завел семью. Родились дети.[/color]\r\n\
Скитаюсь в поисках монет.\r\n\
Без женщин жить нельзя на свете,\r\n\
А с ними – вовсе жизни нет. :D :D [/bgcolor]\r\n\
[bgcolor=#cac8e9][color=#34ff5d]Когда в семейных шумных сварах[/color]\r\n\
Жена бывает неправа,\r\n\
Об этом позже в мемуарах\r\n\
Скорбит прозревшая вдова. \r\n\
[b][spoiler=ЙООООО!!!]ПРИВЕТ, ДАНЯ!![/spoiler][/b]\r\n\
:пох :пох\r\n\
  [/bgcolor]\r\n\
[bgcolor=#a476af][color=#34ff5d]Живя в загадочной отчизне,[/color]\r\n\
Из ночи в день десятки лет\r\n\
Мы пьем за русский образ жизни,\r\n\
Где образ есть, а жизни нет. :глаза :глаза [/bgcolor]"; 
	// #include "s.cpp"
	
	size_t text_len = sizeof(text) - 1; 
	
	cout << text_len << "\n"; 
	struct timeval start_time; 
	struct timeval end_time; 
	TextPreprocessor tp; 
	tp.setText(text, text_len); 
	tp.setUrls(
		"http://evilworlds.ru", sizeof("http://evilworlds.ru") - 1, 
		"http://evilworlds.ru/?url=", sizeof("http://evilworlds.ru/?url=") - 1, 
		"http://i.evilworlds.ru/", sizeof("http://i.evilworlds.ru/") - 1, 
		"http://i.evilworlds.ru/", sizeof("http://i.evilworlds.ru/") - 1
	); 
	tp.setSessionId("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 32); 
	
	gettimeofday(&start_time, NULL); 
	
	tp.analize(); 
	// tp.resize(0, 42, true); 
	tp.prepare(); 
	
	gettimeofday(&end_time, NULL); 
	int sec = end_time.tv_sec - start_time.tv_sec; 
	int usec = end_time.tv_usec - start_time.tv_usec; 
	if (usec < 0) {
		usec = 1000000 - start_time.tv_usec + end_time.tv_usec; 
		sec--; 
	}
	cout << tp.getText() << "\n"; 
	cout << tp.getLength() << "\n"; 
	cout << (float)((float)sec * 1000 + (float)usec / 1000) << " ms\n"; 
	
	return 0; 
}
#endif
